//
// Created by hjie on 23-8-1.
//

#include "remuxing_video_audio.h"
#include <sstream>
#include <unistd.h>

extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/timestamp.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/time.h>

};

static AVFormatContext * input_fmt_ctx = nullptr;
static int video_stream_index = -1;
static int audio_stream_index = -1;
static AVCodecContext * decoder_video_ctx = nullptr;
static AVCodecContext * decoder_audio_ctx = nullptr;

static const std::string output_filename = "./output.ini";
static int fp = 0;

static int OpenCodecContext(int * stream_index, AVCodecContext ** decoder_ctx, AVFormatContext * fmt_ctx, enum AVMediaType media_type)
{
    int result = 0, index = -1;
    AVStream * stream = nullptr;

    result = av_find_best_stream(fmt_ctx, media_type, -1, -1, nullptr, 0);
    if (result < 0)
    {
        printf("Could not find %s stream in input '%s'.\n", av_get_media_type_string(media_type),fmt_ctx->url);
        return -1;
    }
    index = result;
    stream = fmt_ctx->streams[index];

    const AVCodec * dec = avcodec_find_decoder(stream->codecpar->codec_id);
    if (!dec)
    {
        printf("Could not find decoder %d.\n", stream->codecpar->codec_id);
        return -1;
    }
    *decoder_ctx = avcodec_alloc_context3(dec);
    if (!*decoder_ctx)
    {
        printf("Failed to allocate the %s codec context.\n", av_get_media_type_string(media_type));
        return -1;
    }
    result = avcodec_parameters_to_context(*decoder_ctx, stream->codecpar);
    if (result < 0)
    {
        avcodec_free_context(decoder_ctx);
        return -1;
    }
    (*decoder_ctx)->pkt_timebase = stream->time_base;
    result = avcodec_open2(*decoder_ctx, dec, nullptr);
    if (result < 0)
    {
        avcodec_free_context(decoder_ctx);
        printf("Failed to open %s codec.\n", av_get_media_type_string(media_type));
        return -1;
    }
    *stream_index = index;
    return 0;
}

static void ShowFrameData(AVFormatContext  * format_ctx, AVStream * stream, AVCodecContext * codec_ctx, AVFrame * frame)
{
    std::stringstream ss;
    const char * str = av_get_media_type_string(stream->codecpar->codec_type);
    ss << "[FRAME]\r\n";
    if (str)
    {
        ss << "media_type : " << str << "\r\n";
    }
    else
    {
        ss << "media_type : unknown\r\n";
    }
    ss << "stream_index : " << stream->index    << "\r\n";
    ss << "key_frame    : " << frame->key_frame << "\r\n";
    ss << "pts          : " << frame->pts       << "\r\n";

    int64_t pts_time = frame->pts * av_q2d(stream->time_base);
    ss << "pts_time     : " << pts_time       << "s\r\n";
    ss << "pkt_pts      : " << frame->pkt_dts << "\r\n";
    pts_time = frame->pkt_dts * av_q2d(stream->time_base);
    ss << "pkt_dts_time : " << pts_time      << "s\r\n";
    ss << "best_effort_timestamp : " << frame->best_effort_timestamp << "\r\n";
    pts_time = frame->best_effort_timestamp * av_q2d(stream->time_base);
    ss << "best_effort_timestamp_time : " << pts_time << "s\r\n";
    ss << "pkt_duration : " << frame->pkt_duration << "\r\n";
    pts_time = frame->pkt_duration * av_q2d(stream->time_base);
    ss << "pkt_duration_time : " << pts_time << "s\r\n";
    ss << "pkt_pos  : " << frame->pkt_pos  << "\r\n";
    ss << "pkt_size : " << frame->pkt_size << "\r\n";
    ss << "pic_type : " << av_get_picture_type_char(frame->pict_type) << "\r\n";
    ss << "[/FRAME]\r\n";

    // std::cout << ss.str() << std::endl;
    write(fp, ss.str().c_str(), ss.str().size());
}

static int DecodeAudioVideo(AVCodecContext * decode_ctx, AVPacket * packet, AVFrame * frame, int  * is_new_packet)
{
    int result = 0;
    if (*is_new_packet)
    {
        result = avcodec_send_packet(decode_ctx, packet);
        if (result == AVERROR(EAGAIN))
        {
            result = 0;
        }
        else if (result >= 0 || result == AVERROR_EOF)
        {
            *is_new_packet = 0;
            result = 0;
        }
    }
    if (result >= 0)
    {
        int got_frame = 0;
        result = avcodec_receive_frame(decode_ctx, frame);
        if (result >= 0)
        {
            got_frame = 1;
        } else if (result == AVERROR(EAGAIN) || result == AVERROR_EOF)
        {
            result = 0;
            got_frame = 0;
        }
        if (got_frame)
        {
            ShowFrameData(input_fmt_ctx, input_fmt_ctx->streams[packet->stream_index], decode_ctx, frame);
        }
        av_frame_unref(frame);
        if (got_frame)
        {
            return 1;
        }
    }
    return result;
}

static void DecodeVideoAudioData()
{
    int result = 0;
    AVPacket * packet = av_packet_alloc();
    AVFrame  * frame  = av_frame_alloc();
    while (av_read_frame(input_fmt_ctx, packet) >= 0)
    {
        AVCodecContext * decode_ctx = nullptr;
        if (packet->stream_index == audio_stream_index)
        {
            decode_ctx = decoder_audio_ctx;
        }
        else
        {
            decode_ctx = decoder_video_ctx;
        }
        //av_packet_rescale_ts(packet, input_fmt_ctx->streams[packet->stream_index]->time_base, decode_ctx->time_base);
        int is_new_packet = 1;

        while(DecodeAudioVideo(decode_ctx, packet, frame, &is_new_packet) > 0);
        av_packet_unref(packet);
    }
    for (auto  index = 0; index < input_fmt_ctx->nb_streams; index++)
    {
        packet->stream_index = index;
        AVStream * stream = input_fmt_ctx->streams[index];
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            int is_new_packet = 1;
            while(DecodeAudioVideo(decoder_video_ctx, packet, frame, &is_new_packet) > 0);
        }
        if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            int is_new_packet = 1;
            while(DecodeAudioVideo(decoder_audio_ctx, packet, frame, &is_new_packet) > 0);
        }
    }
}

int main(int argc, const char * argv[])
{
    if (argc != 2)
    {
        printf("[usage] : %s input_file video_output_file audio_output_file.\n", argv[0]);
        return EXIT_FAILURE;
    }
    std::string input_filename = argv[1];
/*    std::string output_video_filename = argv[2];
    std::string output_audio_filename = argv[3];*/

    FILE * file_ptr = fopen(output_filename.c_str(), "w+");
    fp = fileno(file_ptr);

    int result = avformat_open_input(&input_fmt_ctx, input_filename.c_str(), NULL, NULL);
    if (result < 0)
    {
        printf("Could not open source file.\n");
        return -1;
    }
    result = avformat_find_stream_info(input_fmt_ctx, nullptr);
    if (result < 0)
    {
        // todo free resource
        printf("Could not find stream information.\n");
        return -1;
    }
    result = OpenCodecContext(&video_stream_index, &decoder_video_ctx, input_fmt_ctx, AVMEDIA_TYPE_VIDEO);
    if (result < 0)
    {
        // free resource
        return EXIT_FAILURE;
    }
    else
    {
        // allocate memory data
    }
    result = OpenCodecContext(&audio_stream_index, &decoder_audio_ctx, input_fmt_ctx, AVMEDIA_TYPE_AUDIO);
    if (result < 0)
    {
        // free resource
        return EXIT_FAILURE;
    }
    DecodeVideoAudioData();

    fclose(file_ptr);
    return EXIT_SUCCESS;
}